/*
 * Copyright (C) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

/**
 * Add to an observable the ability to have change listeners.
 * @author Tomas Shestakov
 */
trait ChangeListeners {

  type Listener = ChangeListeners.Listener

  type Filter = Identifiable.Filter

  /**
   * Add the specified change listener and filter for child observables.
   * if you do not need to filter the children do not specify a filter or use `ChangeListeners.PassAll`.
   * It is allows has only one listener with some filter. Adding the same listener with new filter
   * will produce first `removeChangeListener` then `addChangeListener` with new filter.
   * For example, calling `addChangeListener(l1, f2)` after  `addChangeListener(l1, f1)` will equivalent
   * two calls: `removeChangeListener(l1)`; addChangeListener(l1, f2).
   * @param listener a `PartialFunction[ChangeEvent, Unit]` which gets [[jmmo.engine.property.ChangeEvent]] and executes a code with it.
   * @param filter a predicate which gets `Observable` and returns true if `Observable` pass the filter false otherwise.
   */
  def addChangeListener(listener: Listener, filter: Filter = Identifiable.passAll)

  /**
   * Remove the specified change listener for child observables.
   * An Observable filter on removing must comply the observable filter on adding.
   * @param listener a `PartialFunction[ChangeEvent, Unit]` which gets [[jmmo.engine.property.ChangeEvent]] and executes a code with it.
   */
  def removeChangeListener(listener: Listener)

  /**
   * Sends the specified event to all listeners.
   * @param event event to send
   */
  def fireChangeEvent(event: ChangeEvent)
}

object ChangeListeners {

  /**
   * Type of change listener.
   */
  type Listener = PartialFunction[ChangeEvent, Unit]

  /**
   * if a `listener` parameter is `ListenerWrapper` instance then
   * creates new `ListenerWrapper` with prepend to former `chain` specified container.
   * Else if a `listener` parameter is `ChangeListener.Listener` instance creates
   * new instance of `ListenerWrapper` with specified container as `ListenerWrapper.chain`
   * @param listener a listener to wrap
   * @param container a container to be first in `ListenerWrapper.chain`
   * @return new instance of `ListenerWrapper`
   */
  def wrap(listener: ChangeListeners.Listener, container: ObservableContainer) = listener match {
    case wrapper: ListenerWrapper => wrapper.copy(chain = wrapper.chain :+ container)
    case lis => ListenerWrapper(lis, Vector(container))
  }
}

/**
 * Wrapper for `ChangeListener.Listener` which allow change `ChangeEvent.chain` of sent to listener events.
 * @param wrapped some `ChangeListener.Listener`
 * @param chain new chain of observable containers for sent `ChangeEvent`.
 */
case class ListenerWrapper(wrapped: ChangeListeners.Listener, chain: Seq[ObservableContainer]) extends PartialFunction[ChangeEvent, Unit] {
  def isDefinedAt(event: ChangeEvent) = wrapped.isDefinedAt(event.newChain(chain: _*))
  def apply(event: ChangeEvent) { wrapped.apply(event.newChain(chain: _*)) }
}

/**
 * For filtering parent observable containers from child observables.
 */
trait InverseFilter {

  /**
   * Check if parent observable containers satisfy child observable
   * @param overseer some `Any` from child observable.
   * @return true if overseer value satisfy a condition.
   */
  def inverseFilter(overseer: Any): Boolean
}
